﻿namespace SharpUAC
{
    using System;
    using System.Diagnostics;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Security.Principal;
    using System.Windows.Forms;
    using Microsoft.Win32.SafeHandles;

    // See http://www.codeproject.com/Articles/14358/User-Impersonation-in-NET
    // See http://stackoverflow.com/questions/125341/how-do-you-do-impersonation-in-net
    // See https://github.com/mj1856/SimpleImpersonation

    /// <summary>
    /// An impersonate as utility.
    /// </summary>
    internal class ImpersonateAsUtil
    {
        /// <summary>
        /// Logon user.
        /// </summary>
        ///
        /// <param name="lpszUsername">    The user name. </param>
        /// <param name="lpszDomain">      The domain. </param>
        /// <param name="lpszPassword">    The password. </param>
        /// <param name="dwLogonType">     Type of the log-on. </param>
        /// <param name="dwLogonProvider"> The log-on provider. </param>
        /// <param name="phToken">         The impersonation token. </param>
        ///
        /// <returns>
        /// true if it succeeds, false if it fails.
        /// </returns>
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(
            String lpszUsername,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out SafeTokenHandle phToken);

        /// <summary>
        /// Closes a handle.
        /// </summary>
        ///
        /// <param name="handle"> The handle. </param>
        ///
        /// <returns>
        /// true if it succeeds, false if it fails.
        /// </returns>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(
            IntPtr handle);

        /// <summary>
        /// If you incorporate this code into a DLL, be sure to demand FullTrust.
        /// </summary>
        ///
        /// <exception cref="Win32Exception">   Thrown when a window 32 error
        ///                                     condition occurs. </exception>
        ///
        /// <param name="executable">       The file. </param>
        /// <param name="userName">   Name of the user. </param>
        /// <param name="password">   The password. </param>
        /// <param name="domainName"> Name of the domain. </param>
        [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
        internal static void ImpersonateAsExecute(
            String executable,
            String userName,
            String password,
            String domainName = "")
        {
            SafeTokenHandle safeTokenHandle;
            try
            {
                //! This parameter causes LogonUser to create a primary token. 
                const int LOGON32_LOGON_INTERACTIVE = 2;

                const int LOGON32_PROVIDER_DEFAULT = 0;

                // Call LogonUser to obtain a handle to an access token. 
                bool returnValue = LogonUser(
                    userName,
                    domainName,
                    password,
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT,
                    out safeTokenHandle);

                if (false == returnValue)
                {
                    int ret = Marshal.GetLastWin32Error();

                    MessageBox.Show(String.Format("LogonUser failed with error code : {0}", ret));

                    throw new System.ComponentModel.Win32Exception(ret);
                }

                using (safeTokenHandle)
                {
                    //! Check.
                    //Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
                    //Console.WriteLine("Value of Windows NT token: " + safeTokenHandle);

                    //! Check the identity.
                    //Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

                    //! Use the token handle returned by LogonUser. 
                    using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
                    {
                        using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                        {

                            //! Check the identity.
                            // Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);

                            ProcessStartInfo psi = new ProcessStartInfo(executable);
                            Process.Start(psi);
                        }
                    }

                    //! Releasing the context object stops the impersonation 

                    //! Check the identity.
                    //Console.WriteLine("After closing the context: " + WindowsIdentity.GetCurrent().Name);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception occurred. " + ex.Message, ex.GetType().Name);
            }
        }
        /// <summary>
        /// A safe token handle. This class cannot be inherited.
        /// </summary>
        internal sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            /// <summary>
            /// Prevents a default instance of the SharpUAC.SafeTokenHandle class from
            /// being created.
            /// </summary>
            private SafeTokenHandle()
                : base(true)
            {
                //
            }

            /// <summary>
            /// Closes a handle.
            /// </summary>
            ///
            /// <param name="handle"> The handle. </param>
            ///
            /// <returns>
            /// true if it succeeds, false if it fails.
            /// </returns>
            [DllImport("kernel32.dll")]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            [SuppressUnmanagedCodeSecurity]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool CloseHandle(IntPtr handle);

            /// <summary>
            /// When overridden in a derived class, executes the code required to free
            /// the handle.
            /// </summary>
            ///
            /// <returns>
            /// true if the handle is released successfully; otherwise, in the event of a
            /// catastrophic failure, false. In this case, it generates a
            /// releaseHandleFailed MDA Managed Debugging Assistant.
            /// </returns>
            protected override bool ReleaseHandle()
            {
                return CloseHandle(handle);
            }
        }
    }
}

